﻿--[[
  Aprs4G 诊断打印模块
  ✅ 目的：即使离线/无卡，也能直观看到设备运行状态
  ✅ 特性：
    - 10s 打印：内存 / 网络 / GPS / APRS / MQTT 状态
    - 订阅 GPS FIX / LOSE、APRS 发送、MQTT 状态事件
    - 打印最近一条 GPS 点（FIX 后有效）
    - ✅ 启动时打印设备信息和启动原因
]]

local DIAG_INTERVAL_MS = 10000
local DIAG_DEMO_ENABLE = false
local DIAG_DEMO_INTERVAL_MS = 30000

local cache_ok, cache = pcall(function() return require("cache") end)
local last_point = nil
local gps_fixed = false
local mqtt_state = false
local aprs_ready = false
local loc_src = "?"

local function safe_call(fn, ...)
    local ok, a, b, c, d = pcall(fn, ...)
    if ok then return a, b, c, d end
    return nil
end

-- GPS FIX/LOSE 订阅
sys.subscribe("LOC_GPS_FIXED", function()
    gps_fixed = true
    loc_src = "GPS"
    log.info("diag", "GPS FIX")
end)

sys.subscribe("LOC_GPS_LOSE", function()
    gps_fixed = false
    log.warn("diag", "GPS LOSE")
end)

-- LBS FIX/LOSE 订阅（可选）
sys.subscribe("LOC_LBS_FIXED", function()
    loc_src = "LBS"
    log.info("diag", "LBS FIX")
end)

sys.subscribe("LOC_LBS_LOSE", function()
    log.warn("diag", "LBS LOSE")
end)

-- MQTT 状态订阅
sys.subscribe("mqtt_conack", function()
    mqtt_state = true
end)

-- APRS Ready 状态
sys.subscribe("LOGGED_IN", function() aprs_ready = true end)
sys.subscribe("LOGGED_OUT", function() aprs_ready = false end)

-- APRS 文本预览 + GPS 点记录
sys.subscribe("SEND_APRS_MSG", function(line, point)
    local preview = line or ""
    if #preview > 120 then preview = preview:sub(1, 120).."…" end
    log.info("diag", "APRS out", preview)
    if type(point) == "table" then
        last_point = point
    end
end)

-- msgTab 后备机制（兼容 mixloc 模式）
sys.timerLoopStart(function()
    if type(msgTab) == "table" and #msgTab > 0 then
        last_point = msgTab[#msgTab]
    end
end, 1000)

-- 获取缓存计数
local function cache_count()
    if cache_ok and cache and type(cache.count) == "function" then
        local n = safe_call(cache.count)
        if type(n) == "number" then return n end
    end
    return -1
end

-- 网络状态
local function net_brief()
    local st = -1
    if mobile and mobile.status then st = (safe_call(mobile.status)) or -1 end
    local csq, ber = safe_call(mobile.csq)
    return st, csq, ber
end

-- 实时 GNSS 概览（优先显示实时数据）
local function gnss_brief()
    if not libgnss then return nil end
    local fixed = safe_call(libgnss.isFix)
    local gsv = safe_call(libgnss.getGsv) or {}
    local satview = tonumber(gsv.total_sats) or 0
    if fixed then
        local rmc = safe_call(libgnss.getRmc, 0) or {}
        local gga = safe_call(libgnss.getGga, 2) or {}
        local lat = tonumber(rmc.lat)
        local lng = tonumber(rmc.lng)
        local spd_kn = tonumber(rmc.speed) or 0
        local hdop = tonumber(gga.hdop) or 0
        local satuse = tonumber(gga.satellites_tracked) or 0
        local alt_ft = math.floor(((tonumber(gga.altitude) or 0) * 3.2808399))
        return {
            fix = true,
            lat = lat, lng = lng,
            spd = spd_kn, alt = alt_ft,
            hdop = hdop, satuse = satuse, satview = satview,
            time = os.time(), gps = true,
            latT = lat and (lat >= 0 and "N" or "S") or nil,
            lngT = lng and (lng >= 0 and "E" or "W") or nil,
        }
    end
    return { fix = false, satview = satview }
end

local function fmt_dm(dm, hemi, is_lat)
    if type(dm) ~= "number" then return "?" end
    if is_lat then
        return string.format("%07.2f%s", math.abs(dm), hemi or "")
    else
        return string.format("%08.2f%s", math.abs(dm), hemi or "")
    end
end

-- 启动设备简报
local function dev_brief()
    local imei = (mobile and mobile.imei and mobile.imei()) or "?"
    local model = (mobile and mobile.model and mobile.model()) or "?"
    local dtype = DEV_TYPE or "?"
    local rst = rtos and rtos.poweronReason and rtos.poweronReason() or "?"
    return imei, model, dtype, rst
end

-- fskv 状态
local function kv_brief()
    if fskv and fskv.status then
        local used, total, cnt = fskv.status()
        return used, total, cnt
    end
    return nil
end

-- 主诊断任务
sys.taskInit(function()
    sys.wait(3000)
    local imei, model, dtype, rst = dev_brief()
    log.info("diag", "IMEI", imei, "MODEL", model, "DEV", dtype, "REASON", rst)
    if rtos and rtos.poweronReason then
        local raw = rtos.poweronReason()
        log.info("diag", "PWRON_RAW", raw)
    end

    local used, total, cnt = kv_brief() or {}, {}, {}
    if used then log.info("diag", "kv", "used", used, "total", total, "count", cnt) end

    while true do
        if rtos and rtos.meminfo then
            local lf, lt = rtos.meminfo("sys")
            log.info("diag", "mem", "lua_free", lf, "total", lt)
        end

        local st, csq, ber = net_brief()
        log.info("diag", "net", "status", st, "csq", csq or "?", "ber", ber or "?", "aprs", aprs_ready and 1 or 0, "mqtt", mqtt_state and 1 or 0)

        local n = cache_count()
        if n >= 0 then log.info("diag", "cache", "count", n) end

        local live = gnss_brief()
        if live then
            log.info("diag", "gsv", "visible", live.satview or 0)
        end
        local p = (live and live.fix) and live or last_point
        if p then
            local ts = p.time or os.time()
            local t = os.date("*t", ts)
            local hhmmss = string.format("%02d:%02d:%02d", t.hour, t.min, t.sec)
            local age = os.time() - ts
            local kn = tonumber(p.spd or 0) or 0
            local kmh = string.format("%.1f", kn * 1.852)
            local lat_str = fmt_dm(p.lat or 0, p.latT, true)
            local lng_str = fmt_dm(p.lng or 0, p.lngT, false)
            log.info("diag", "gps",
                "fix", (p.gps and gps_fixed) and 1 or 0,
                "src", loc_src,
                "time", hhmmss, "age(s)", age,
                "lat", lat_str, "lng", lng_str,
                "spd(km/h)", kmh, "hdop", p.hdop or 0,
                "sat", string.format("%d/%d", p.satuse or 0, p.satview or 0),
                "alt(ft)", p.alt or 0)
        else
            log.info("diag", "gps", "no point yet", "src", loc_src, "fix", gps_fixed and 1 or 0)
        end

        log.info("diag", "mqtt", "connected", mqtt_state and 1 or 0)

        sys.wait(DIAG_INTERVAL_MS)
    end
end)

-- 离线演示 APRS（可选）
sys.taskInit(function()
    if not DIAG_DEMO_ENABLE then return end
    sys.waitUntil("CFGLOADED")
    while true do
        local now = os.date("*t")
        local msg = string.format("%s>APRS4G,TCPIP*:/%02d%02d%02dh%07.2fN/%08.2fE>%s demo",
            (aprscfg and aprscfg.sourceCall) or "NOCALL-0", (aprscfg and aprscfg.VIA_CALL) or "BD3ND",
            now.hour, now.min, now.sec,
            4319.80, 12533.00, ">")
        sys.publish("SEND_APRS_MSG", msg)
        sys.wait(DIAG_DEMO_INTERVAL_MS)
    end
end)


